﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.IO;
using ICSharpCode.SharpZipLib.Zip;

namespace KC.DataAccess.Compression
{
	public class Zip
	{
		/// <summary>Zips files.</summary>
		/// <param name="inputFolderPath">Zip all files in this directory</param>
		/// <param name="outputPathAndFile">Fully qualified ZIP file to generate</param>
		/// <param name="password">Password to apply to ZIP file</param>
		public static void ZipFiles(string inputFolderPath, string outputPathAndFile, string password)
		{
			ArrayList filenames = BuildFileList(inputFolderPath);
			int TrimLength = (Directory.GetParent(inputFolderPath)).ToString().Length + 1;
			FileStream inStream;
			byte[] obuffer;
			string outPath = inputFolderPath + @"\" + outputPathAndFile;
			ZipOutputStream outStream = new ZipOutputStream(File.Create(outPath));
			if (password != null && password != String.Empty)
				outStream.Password = password;
			outStream.SetLevel(9);
			ZipEntry zipEntry;

			foreach (string inFilename in filenames) {
				zipEntry = new ZipEntry(inFilename.Remove(0, TrimLength));
				outStream.PutNextEntry(zipEntry);

				if (!inFilename.EndsWith(@"/")) {	// Handle directories
					inStream = File.OpenRead(inFilename);
					obuffer = new byte[inStream.Length];
					inStream.Read(obuffer, 0, obuffer.Length);
					outStream.Write(obuffer, 0, obuffer.Length); } }

			outStream.Finish();
			outStream.Close();
		}
		/// <summary>Unzips files.</summary>
		/// <param name="zipFilename">Fully qualified filename to unzip</param>
		/// <param name="deleteZipFile">Delete the ZIP when done?</param>
		/// <param name="outputFolder">Directory to unzip to. If null, unzips to same directory</param>
		/// <param name="password">Password to unlock ZIP file</param>
		public static void UnZipFiles(string zipFilename, bool deleteZipFile, string outputFolder = null, string password = null)
		{
			// Validation
			if (string.IsNullOrEmpty(zipFilename)) throw new ArgumentNullException("zipFilename");
			if (!System.IO.File.Exists(zipFilename)) throw new FileNotFoundException("File not found: " + zipFilename);
			if (string.IsNullOrEmpty(outputFolder)) outputFolder = new FileInfo(zipFilename).DirectoryName + Path.GetFileNameWithoutExtension(zipFilename);
			if (!Directory.Exists(outputFolder))
				try { Directory.CreateDirectory(outputFolder); }
				catch { outputFolder = new FileInfo(zipFilename).DirectoryName; }
			if (!outputFolder.EndsWith("\\")) outputFolder += "\\";

			ZipInputStream inStream = new ZipInputStream(File.OpenRead(zipFilename));
			if (!string.IsNullOrEmpty(password))
				inStream.Password = password;

			ZipEntry zipEntry;
			while ((zipEntry = inStream.GetNextEntry()) != null) {
				string fileName = Path.GetFileName(zipEntry.Name);
				if (fileName != String.Empty /* && zipEntry.Name.IndexOf(".ini") < 0 */) {
					string 
						OutputFile = outputFolder + zipEntry.Name.Replace("\\ ", "\\"),
						fullDirPath = Path.GetDirectoryName(OutputFile);
					if (!Directory.Exists(fullDirPath)) Directory.CreateDirectory(fullDirPath);

					FileStream streamWriter = File.Create(OutputFile);
					int size = 2048;
					byte[] data = new byte[2048];
					while (true) {
						size = inStream.Read(data, 0, data.Length);
						if (size > 0)
							streamWriter.Write(data, 0, size);
						else
							break; }
					streamWriter.Close(); } }
			inStream.Close();
			if (deleteZipFile)
				File.Delete(zipFilename);
		}
		/// <summary>Builds a recursive list of fully-qualified local filenames in a given directory</summary>
		/// <param name="Dir">Directory to grep files from</param>
		/// <returns>List of fully-qualified filenames</returns>
		private static ArrayList BuildFileList(string Dir)
		{
			ArrayList filenames = new ArrayList();
			foreach (string file in Directory.GetFiles(Dir)) 
				filenames.Add(file);

			if (filenames.Count == 0 && Directory.GetDirectories(Dir).Length == 0)
				filenames.Add(Dir + @"/");

			foreach (string dirs in Directory.GetDirectories(Dir))
				foreach (object obj in BuildFileList(dirs))
					filenames.Add(obj);

			return filenames;
		}
	}
}
